package wmsignerjx;

import java.util.Arrays;

public final class MD4 {

	private static final int INT_BITS = 32;
	private static final int BYTE_BITS = 8;
	private static final int INT_BYTES = 4;

	private static final int COUNT_SIZE = 8;
	private static final int BLOCK_SIZE = 64;
	private static final int FINAL_SCOPE = 56;

	private static final int INT_BLOCK_SIZE = BLOCK_SIZE / INT_BYTES;

	private static final int I0 = 0x67452301;
	private static final int I1 = 0xEFCDAB89;
	private static final int I2 = 0x98BADCFE;
	private static final int I3 = 0x10325476;

	private static final int C2 = 0x5A827999;
	private static final int C3 = 0x6ED9EBA1;

	private static final int FS1 = 3;
	private static final int FS2 = 7;
	private static final int FS3 = 11;
	private static final int FS4 = 19;

	private static final int GS1 = 3;
	private static final int GS2 = 5;
	private static final int GS3 = 9;
	private static final int GS4 = 13;

	private static final int HS1 = 3;
	private static final int HS2 = 9;
	private static final int HS3 = 11;
	private static final int HS4 = 15;

	private MD4() {
		// no instance
	}

	public static byte[] getByteHash(byte[] array) {
		return getByteHash(array, 0, array.length);
	}

	public static byte[] getByteHash(byte[] array, int ibStart, int size) {
		return Buffer.GetBytes(getInt32Hash(array, ibStart, size));
	}

	public static int[] getInt32Hash(byte[] array) {
		return getInt32Hash(array, 0, array.length);
	}

	public static int[] getInt32Hash(byte[] array, int ibStart, int size) {
		final int[] context = new int[] { I0, I1, I2, I3 }; // CONTEXT_SIZE = 4
		final int[] block = new int[INT_BLOCK_SIZE];

		int len = ibStart + size;
		int pos = ibStart;
		for (; pos <= len - BLOCK_SIZE; pos += BLOCK_SIZE) {
			Buffer.BlockCopy(array, pos, block, 0, BLOCK_SIZE);
			transformBlock(block, context);
		}

		final byte[] count = new byte[COUNT_SIZE];
		long bitsCount = (long) len * BYTE_BITS;
		Buffer.CopyBytes(bitsCount, count);
		// ByteBuffer.wrap(count).order(ByteOrder.LITTLE_ENDIAN).putLong(bitsCount);

		Arrays.fill(block, 0);

		Buffer.BlockCopy(array, pos, block, 0, len - pos);
		transformFinalBlock(block, len - pos, count, context);

		return context;
	}

	private final static void transformFinalBlock(int[] block, int len, byte[] count, int[] context) {
		Buffer.BlockCopy(new byte[]{ (byte) 0x80 }, 0, block, len, 1);

		if (len < FINAL_SCOPE) {
			Buffer.BlockCopy(count, 0, block, FINAL_SCOPE, COUNT_SIZE);
			transformBlock(block, context);
		} else {
			transformBlock(block, context);
			Arrays.fill(block, 0);
			Buffer.BlockCopy(count, 0, block, FINAL_SCOPE, COUNT_SIZE);
			transformBlock(block, context);
		}
	}

	private final static void transformBlock(final int[] value, final int[] context) {
		int a = context[0];
		int b = context[1];
		int c = context[2];
		int d = context[3];

		a = ff(a, b, c, d, value[0], FS1);
		d = ff(d, a, b, c, value[1], FS2);
		c = ff(c, d, a, b, value[2], FS3);
		b = ff(b, c, d, a, value[3], FS4);
		a = ff(a, b, c, d, value[4], FS1);
		d = ff(d, a, b, c, value[5], FS2);
		c = ff(c, d, a, b, value[6], FS3);
		b = ff(b, c, d, a, value[7], FS4);
		a = ff(a, b, c, d, value[8], FS1);
		d = ff(d, a, b, c, value[9], FS2);
		c = ff(c, d, a, b, value[10], FS3);
		b = ff(b, c, d, a, value[11], FS4);
		a = ff(a, b, c, d, value[12], FS1);
		d = ff(d, a, b, c, value[13], FS2);
		c = ff(c, d, a, b, value[14], FS3);
		b = ff(b, c, d, a, value[15], FS4);

		a = gg(a, b, c, d, value[0], GS1);
		d = gg(d, a, b, c, value[4], GS2);
		c = gg(c, d, a, b, value[8], GS3);
		b = gg(b, c, d, a, value[12], GS4);
		a = gg(a, b, c, d, value[1], GS1);
		d = gg(d, a, b, c, value[5], GS2);
		c = gg(c, d, a, b, value[9], GS3);
		b = gg(b, c, d, a, value[13], GS4);
		a = gg(a, b, c, d, value[2], GS1);
		d = gg(d, a, b, c, value[6], GS2);
		c = gg(c, d, a, b, value[10], GS3);
		b = gg(b, c, d, a, value[14], GS4);
		a = gg(a, b, c, d, value[3], GS1);
		d = gg(d, a, b, c, value[7], GS2);
		c = gg(c, d, a, b, value[11], GS3);
		b = gg(b, c, d, a, value[15], GS4);

		a = hh(a, b, c, d, value[0], HS1);
		d = hh(d, a, b, c, value[8], HS2);
		c = hh(c, d, a, b, value[4], HS3);
		b = hh(b, c, d, a, value[12], HS4);
		a = hh(a, b, c, d, value[2], HS1);
		d = hh(d, a, b, c, value[10], HS2);
		c = hh(c, d, a, b, value[6], HS3);
		b = hh(b, c, d, a, value[14], HS4);
		a = hh(a, b, c, d, value[1], HS1);
		d = hh(d, a, b, c, value[9], HS2);
		c = hh(c, d, a, b, value[5], HS3);
		b = hh(b, c, d, a, value[13], HS4);
		a = hh(a, b, c, d, value[3], HS1);
		d = hh(d, a, b, c, value[11], HS2);
		c = hh(c, d, a, b, value[7], HS3);
		b = hh(b, c, d, a, value[15], HS4);

		context[0] += a;
		context[1] += b;
		context[2] += c;
		context[3] += d;
	}

	private static final int rot(int t, int s) {
		int result = (t << s) | (t >>> (INT_BITS - s));
		return result;
	}

	private static final int f(int x, int y, int z) {
		int t = (x & y) | (~x & z);
		return t;
	}

	private static final int g(int x, int y, int z) {
		int t = (x & y) | (x & z) | (y & z);
		return t;
	}

	private static final int h(int x, int y, int z) {
		int t = x ^ y ^ z;
		return t;
	}

	private static final int ff(int a, int b, int c, int d, int x, int s) {
		int t = a + f(b, c, d) + x;
		return rot(t, s);
	}

	private static final int gg(int a, int b, int c, int d, int x, int s) {
		int t = a + g(b, c, d) + x + C2;
		return rot(t, s);
	}

	private static final int hh(int a, int b, int c, int d, int x, int s) {
		int t = a + h(b, c, d) + x + C3;
		return rot(t, s);
	}
}
